package com.lavender.transaction.transaction.impl;

import com.lavender.transaction.entity.Account;
import com.lavender.transaction.entity.User;
import com.lavender.transaction.service.AccountService;
import com.lavender.transaction.service.UserService;
import com.lavender.transaction.transaction.TransactionInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 测试JDBC事务
 * </p>
 *
 * @author: zhu.chen
 * @date: 2020/12/2
 * @version: v1.0.0
 */
@Service
public class TransactionTestImpl implements TransactionInterface {

    @Autowired
    private UserService userService;

    @Autowired
    private AccountService accountService;

    @Transactional
    @Override
    public String testTransaction() {
        insertUser();

        // 1 : 遇到RuntimeException则回滚。
        //testRollbackForRuntimeException();

        // 2 : 遇到Exception则回滚。
        //testRollbackForException();

        // 3 : 只读事务。
        //testReadOnly();

        // 4 : 设置超时时间事务
        //testTimeOut();

        // 5：访问权限对@Transactional的影响
        //testMethodPermissions();

        // 6：测试事务传播属性
        testPropagation();

        return "ok";
    }

    /**
     * @Transactional(rollbackFor = RuntimeException.class) ：遇到RuntimeException则回滚
     */
    private void testRollbackForRuntimeException() {
        insertAccount();
        throw new RuntimeException("插入账户错误");
    }

    /**
     * @Transactional(rollbackFor = Exception.class) ：遇到Exception则回滚
     */
    private void testRollbackForException() throws Exception {
        insertAccount();
        throw new Exception("插入账户错误");
    }

    /**
     * @Transactional(readOnly = true) ：读操作能执行，写操作不能执行。
     * <p>
     * 事务执行失败：java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
     */
    private void testReadOnly() {
        insertAccount();
    }

    /**
     * @Transactional(timeout = 3) ：事务执行操作，则事务执行失败。
     * <p>
     * 事务执行失败：org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Wed Dec 02 15:41:35 CST 2020
     */
    private void testTimeOut() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        insertAccount();
    }

    /**
     * 测试方法的访问权限对事务的影响：
     *
     * @Transactional : 只能被应用到public方法上，对于其它非public ( protected、默认方法[不写访问权限控制符] ) 的方法，如果标记了@Transactional也不会报错，但方法没有事务功能。private方法标记了会报错。
     *
     */
    @Transactional(rollbackFor = RuntimeException.class)
    protected void testMethodPermissions() {
        insertAccount();
        throw new RuntimeException("插入账户错误");
    }

    /**
     * 测试事务的传播属性。
     * propagation：事务传播属性，仅仅是spring声明用于事务编码的属性，关系型数据库中无该属性。
     *
     * 事务的传播属性propagation适用场景：事务方法A调用事务方法B时使用。
     * 若应用中只有1个事务方法B，则使用事务的默认传播属性REQUIRED即可，则会新建一个事务。
     * e.g：
     * public class A {
     *
     *  @Autowired
     * 	private B b;
     *
     * 	@Transactional
     * 	public void testA(){
     * 		xxx;
     * 		b.testB();
     * 	}
     * }
     *
     * public class B {
     *
     * 	@Transactional
     * 	public void testB(){
     * 		xxx;
     * 	}
     *
     * }
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void testPropagation(){
        insertAccount();
    }

    private void insertUser() {
        User user = new User();
        user.setUserId("admin");
        user.setPassword("123456");
        userService.insert(user);
    }

    private void insertAccount() {
        Account account = new Account();
        account.setUserId("admin");
        account.setAccountNum("gs123456");
        account.setMoney(10000);
        accountService.insert(account);
    }

}
